﻿//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Microsoft Public License.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace AstoriaOverAstoria
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>Given expression of which evaluates to ProjectedWrapper
    /// this class finds the init expression for the ProjectedWrapper and determines
    /// the expression which evaluates to the underlying entity being projected.</summary>
    internal class ProjectedWrapperEntityPathExtractor : ExpressionVisitor
    {
        /// <summary>The path we've found.</summary>
        private Expression resultPath;

        /// <summary>Determines the entity path expression from the specified <paramref name="expression"/>.</summary>
        /// <param name="expression">The expression to inspect.</param>
        /// <returns>The expression which evaluates to the entity being projected.</returns>
        public static Expression GetEntityPath(Expression expression)
        {
            Debug.Assert(expression != null, "expression != null");
            Debug.Assert(TypeSystem.IsProjectedWrapperType(expression.Type), "This visitor only works on ProjectedWrapper types.");

            ProjectedWrapperEntityPathExtractor visitor = new ProjectedWrapperEntityPathExtractor();
            visitor.Visit(expression);
            return visitor.resultPath;
        }

        /// <summary>Visits a member init expression.</summary>
        /// <param name="init">The member init expression.</param>
        /// <returns>The visited expression.</returns>
        internal override Expression VisitMemberInit(MemberInitExpression init)
        {
            if (TypeSystem.IsProjectedWrapperType(init.Type))
            {
                if (this.resultPath == null)
                {
                    // Get the first projected property which doesn't have a constant assigned to it
                    MemberAssignment assignment = init.Bindings.OfType<MemberAssignment>().
                        FirstOrDefault(a => a.Member.Name.StartsWith("ProjectedProperty") &&
                            !(ExpressionUtil.RemoveConversionsAndTypeAs(a.Expression) is ConstantExpression));

                    if (assignment != null)
                    {
                        // Remove the last property access since we're projecting a property value, but we want
                        //   the underlying entity
                        MemberExpression member = ExpressionUtil.RemoveConversionsAndTypeAs(assignment.Expression) as MemberExpression;
                        if (member != null)
                        {
                            this.resultPath = ExpressionUtil.RemoveConversionsAndTypeAs(member.Expression);
                        }
                    }
                }

                return init;
            }

            return base.VisitMemberInit(init);
        }
    }
}
